﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Threading;

namespace SpinDotters.Core
{
    /// <summary>
    /// Spinning progress bar
    /// </summary>
    [TemplatePart(Name = "PART_UpDown", Type = typeof(DomainUpDown))]
    public class ProgressBar : RangeBase
    {
        private DomainUpDown progressUpDown;
        private BackgroundWorker indeterminateBackgroundWorker;
        private int indeterminateAnimationDelay;

        /// <summary>
        /// IsIndeterminate dependency property.
        /// </summary>
        public static readonly DependencyProperty IsIndeterminateProperty =
            DependencyProperty.Register(
                "IsIndeterminate",
                typeof(bool),
                typeof(ProgressBar),
                new PropertyMetadata(OnLayoutChanged));

        /// <summary>
        /// Indicates whether or not the progress bar reports generic progress or progress based on the Value property.
        /// </summary>
        public bool IsIndeterminate
        {
            get { return (bool)GetValue(IsIndeterminateProperty); }
            set { SetValue(IsIndeterminateProperty, value); }
        }

        /// <summary>
        /// Identifies the SegmentBrush dependency property.
        /// </summary>
        public static readonly DependencyProperty SegmentBrushProperty =
            DependencyProperty.Register(
                "SegmentBrush",
                typeof(Brush),
                typeof(ProgressBar),
                null
            );

        /// <summary>
        /// Gets or sets the segment brush.
        /// </summary>
        [Category("SpinDotters")]
        public Brush SegmentBrush
        {
            get { return (Brush)GetValue(SegmentBrushProperty); }
            set { SetValue(SegmentBrushProperty, value); }
        }

        /// <summary>
        /// Orientation dependency property.
        /// </summary>
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register(
                "Orientation",
                typeof(Orientation),
                typeof(ProgressBar),
                new PropertyMetadata(new PropertyChangedCallback(OnLayoutChanged))
            );

        /// <summary>
        /// Gets or sets the spin orientation.
        /// </summary>
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }


        /// <summary>
        /// Constructor
        /// </summary>
        public ProgressBar()
        {
            this.DefaultStyleKey = typeof(ProgressBar);

            this.ValueChanged += new RoutedPropertyChangedEventHandler<double>(SpinningProgressBar_ValueChanged);

            this.indeterminateBackgroundWorker = new BackgroundWorker();
            this.indeterminateBackgroundWorker.WorkerSupportsCancellation = true;
            this.indeterminateBackgroundWorker.WorkerReportsProgress = true;
            this.indeterminateBackgroundWorker.DoWork +=
                new DoWorkEventHandler(indeterminateBackgroundWorker_DoWork);
            this.indeterminateBackgroundWorker.ProgressChanged +=
                new ProgressChangedEventHandler(indeterminateBackgroundWorker_ProgressChanged);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.progressUpDown = this.GetTemplateChild("PART_UpDown") as DomainUpDown;

            SetVisualState();
        }

        private void SpinningProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            this.progressUpDown.SelectedIndex = (int)(this.progressUpDown.Items.Count *
                ((this.Value * 0.9999 - this.Minimum) / (this.Maximum - this.Minimum)));
        }

        private void SetVisualState()
        {
            if (this.progressUpDown != null)
            {
                // cancel background worker
                if (this.indeterminateBackgroundWorker.IsBusy)
                    this.indeterminateBackgroundWorker.CancelAsync();

                this.progressUpDown.Items.Clear();

                if (this.IsIndeterminate)
                {
                    for (int i = 0; i < 6; i++)
                    {
                        SegmentBar segmentBar = new SegmentBar();
                        segmentBar.SegmentBrush = this.SegmentBrush;
                        segmentBar.Orientation = this.Orientation;
                        segmentBar.Segments = 50;

                        if (i > 0)
                            segmentBar.StartSegment = (i-1)*10;

                        if (i < 5)
                            segmentBar.EndSegment = (i+1)*10;

                        this.progressUpDown.Items.Add(segmentBar);
                        this.progressUpDown.AnimationTime = 600;
                        this.indeterminateAnimationDelay = 700;
                    }

                    this.indeterminateBackgroundWorker.RunWorkerAsync(this);
                }
                else
                {
                    for (int i = 0; i <= 50; i += 2)
                    {
                        SegmentBar segmentBar = new SegmentBar();
                        segmentBar.SegmentBrush = this.SegmentBrush;
                        segmentBar.Orientation = this.Orientation;
                        segmentBar.Segments = 50;
                        segmentBar.EndSegment = i;
                        this.progressUpDown.Items.Add(segmentBar);
                        this.progressUpDown.AnimationTime = 1000;
                    }

                    // adjust progress bar to current value
                    this.SpinningProgressBar_ValueChanged(this, null);
                }

                // orientation of progressbar and updown are opposed
                if (this.Orientation == Orientation.Horizontal)
                {
                    this.progressUpDown.Orientation = Orientation.Vertical;

                    // restrict height of segment bar
                    foreach (SegmentBar segmentBar in this.progressUpDown.Items)
                        segmentBar.Height = this.Height;
                }
                else
                {
                    this.progressUpDown.Orientation = Orientation.Horizontal;

                    // restrict width of segment bar
                    foreach (SegmentBar segmentBar in this.progressUpDown.Items)
                        segmentBar.Width = this.Width;
                }
            }
        }

        private static void OnLayoutChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((ProgressBar)o).SetVisualState();
        }

        static void indeterminateBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                ProgressBar spb = (ProgressBar)e.UserState;

                spb.progressUpDown.SelectedIndex++;
            }
            catch (InvalidOperationException) { }
        }

        static void indeterminateBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;
            ProgressBar spb = (ProgressBar)e.Argument;

            while (!bw.CancellationPending)
            {
                Thread.Sleep(spb.indeterminateAnimationDelay);

                // change control in UI thread
                if (!bw.CancellationPending)
                    bw.ReportProgress(0, e.Argument);
            }
        }
    }
}
